
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	DrawingView.m
 *
 *	This view represents the page that the image is drawn onto. It is
 *	a subview of the DocView. The DocView is the document view of
 *	the ClipView. This view's real size grows with the scale but the
 *	bounds always stays the same.
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
 */

#import "DrawingView.h"
#import "DrawingViewWraps.h"
#import "DocView.h"
#import "TextApp.h"

#import "hdshowaux.h"

#import <appkit/Button.h>
#import <appkit/Cell.h>
#import <appkit/Font.h>
#import <appkit/Matrix.h>
#import <appkit/nextstd.h>

#import <dpsclient/dpsclient.h>
#import <dpsclient/wraps.h>

#import <ctype.h>

extern char*const	textstrings[];
extern float		textstart[][2];

@implementation DrawingView

/*
*	Allocate a gstate, set the clipping to NO because it will be clipped
*	by the clip view.
*/
+newFrame:(NXRect *) frm
{
	id		font;

	self = [super newFrame:frm]; 

	[[[self allocateGState] setClipping:NO]  setFlipped:YES]; 
	NX_MALLOC(charspace, NXCoord, MAX_XSHOW);
	AllocShowStruct(&s);

	showtype = XSHOW;				/* use xshow */
	cache = YES;						/* use font cache */

	fontsize = FONTSIZE;
	font = [Font  newFont:"Times-Roman"  size:fontsize];
	PSWCopyFont("Times-Roman", "Times-RomanOutline");
	
	PSWCurrentcacheparams(&size, &lower, &upper);

	return self;
}

- free
{
	PSWSetcacheparams(size, lower, upper);
	NXPing();

	NX_FREE(charspace);
	FreeShowStruct(&s);

	return [super free];
}

- (int) rotation
{
	return rotation;
}

- fontsize:sender
{
	fontsize = atof([[sender  selectedCell]  title]);

	[self  reshow:self];

	return self;
}

- justify:sender
{
	justify = ([sender selectedTag] != 0);  
	[self  reshow:self];

	return self;
}

- kern:sender
{
	kern = [[sender selectedCell]  state];
	[self  reshow:self];

	return self;
}

- track:sender
{
	track = [[sender selectedCell]  state];
	[self  reshow:self];

	return self;
}

/*
*	Sets the font cache to an arbitrarily low size. It's almost never necessary
*	to change the font cache size. This is only here to provide some
*	demonstration of the effectiveness of the font cache. Its operation is
*	best left alone.
*/
- cache:sender
{
	cache = [[sender selectedCell]  state];  
	if (cache)
		PSWSetcacheparams(size, lower, upper);
	else
		PSWSetcacheparams(0, 0, 0);
	
	[self  reshow:self];

	return self;
}

- outline:sender
{
	outline = [[sender selectedCell]  state];
	[self  reshow:self];

	return self;
}

- show:sender
{
	showtype = [sender selectedTag];  
	[self  reshow:self];

	return self;
}

- compareKerns:sender
{
	id		cell;

	compareKern = [[sender  selectedCell]  state];
	if (compareKern && compareWidths)
	{
		compareWidths = NO;
		cell = [[NXApp  comparisonsMatrix]  findCellWithTag:WIDTH_TAG];
		[cell  setState:NO];
		[[cell controlView]  drawCell:cell];
	}

	[self  reshow:self];

	return self;
}

- compareWidths:sender
{
	id		cell;

	compareWidths = [[sender  selectedCell]  state];
	if (compareWidths && compareKern)
	{
		compareKern = NO;
		cell = [[NXApp  comparisonsMatrix]  findCellWithTag:KERN_TAG];
		[cell  setState:NO];
		[[cell controlView]  drawCell:cell];
	}

	[self  reshow:self];

	return self;
}

- rotate:sender
{
	id			field;

	field = [NXApp rotationField];

	if (sender == field)
		rotation = atoi([field  stringValue]);
	else if ([sender selectedTag] == 0)
		rotation += ROTATION;
	else
		rotation -= ROTATION;

	rotation = rotation % 360;
	[field  setIntValue:rotation];

	[self  resetFields:self];
	[self  rotateTo:(float) rotation];
	[superview  rotateDrawView];
	[self  displayFields:self];

	return self;
}

/*
*	Changes the title of the menu cell according
*	to the value of the trace variable.
*/
-trace:sender
{
	if (trace == NO)
		[[sender selectedCell] setTitle:"Trace On"];
	else
		[[sender selectedCell] setTitle:"Trace Off"];

	trace = !trace;
	
	return self;
}

- eraseFields:sender
{
	id		matrixId;

	int		i;

	matrixId = [NXApp  timingMatrix];
	for (i= 0; i < [matrixId cellCount]; i++)
		[[matrixId cellAt:i :0] setStringValue:""];

	matrixId = [NXApp  statusMatrix];
	for (i= 0; i < [matrixId cellCount]; i++)
		[[matrixId cellAt:i :0] setStringValue:""];

	eraseFields = NO;

	return self;
}

- resetFields:sender
{
	/* Prepare for next time around. */
	timing_info.chars = timing_info.reshows = timing_info.kerns = timing_info.time = 0;

	return self;
}

- displayFields:sender
{
	id		matrixId;

	int		i, cvalues[7];
	
	 if (timing_info.reshows)
	{
		matrixId = [NXApp timingMatrix];
		[[matrixId cellAt:0 :0]  setIntValue:rint(timing_info.time/timing_info.reshows)];
		[[matrixId cellAt:1 :0]  setIntValue:rint(timing_info.chars/timing_info.reshows)];
		[[matrixId cellAt:2 :0]  setIntValue:rint(timing_info.kerns/timing_info.reshows)];

		matrixId = [NXApp statusMatrix];
		PSWCachestatus(cvalues);
		for (i= 0; i < [matrixId  cellCount]; i++)
			[[matrixId cellAt:i :0] setIntValue:cvalues[i]];

	}

	[self  resetFields:self];
	eraseFields = YES;

	return self;
}

- reshow:sender
{
	NXRect	visRect;

	eraseFields = NO;

	[self  resetFields:self];
	[self  getVisibleRect:&visRect];
	[self  display:&visRect :1];

	[self  displayFields:self];

	return self;
}

/*
*	If the docview is zooming, then scale the drawing view.
*/
- mouseDown:(NXEvent *)event
{
	NXPoint		p;
	
 	p = event->location; 
	if ([superview  isZooming])
	{
		eraseFields = NO;
		[self  resetFields:self];
		[superview  scaleDrawViewToPoint:&p];
		[self  displayFields:self];
	}

	return self;
}

static NXCoord filltrackwidths(char *str, float *array, ShowStruct *s, float value)
{
	int			i, len;

	NXCoord		trackwidth = 0.0;

	if (str && value)
	{
		len = strlen(str);
		if (array)
		{
			for (i = 0; i < len; i++)
				array[i] += value;
		}
		else if (s)
			AddTracking(s, 0, value);

		trackwidth = len * value;
	}

	return trackwidth;
}

static void fillspacewidths(char *str, float *array, ShowStruct *s, float value)
{
	int			i, len;

	if (str && value)
	{
		if (array)
		{
			len = strlen(str);
			for (i = 0; i < len; i++)
				if (str[i] == ' ')
					array[i] += value;
		}
		else if (s)
			AddSpaceAdj(s, 0, value);
	}
}

/*
*	If an array is passed in then place the character width for each character
*	in str in it (used when displaying with xshow). Also keep track of the total width
*	and the number of spaces and return the value of the width divided by the
*	spaces (used when displaying with full justification).
*/
static NXCoord fillstringwidths(char *str, NXFontMetrics *metrics, float *array, float fontsize, NXCoord kernwidth, NXCoord trackwidth)
{
	BOOL		nospacing = NO;

	int			i, len,
				spaces;

	NXCoord		value,
				linewidth;

	spaces = 0;
	linewidth = kernwidth + trackwidth;
	if (str && metrics && metrics->widths)
	{
		len = strlen(str);
		for (i = 0; i < len; i++)
		{
			value = metrics->widths[(unsigned char)str[i]] * fontsize;
			linewidth += value;
			if (array)
				array[i] += value;

			/* Check for space, return, linefeed, etc. */
			if(isspace(str[i]))
			{
				if (str[i] == ' ')
					spaces ++;
				else
					nospacing = YES;
			}
		}
	}

	if (spaces == 0)
		value = 0.0;
	else
	{
		if (trackwidth)
			value = (LINE_LENGTH_TR - linewidth)/spaces;
		else
			value = (LINE_LENGTH - linewidth)/spaces;
		if (nospacing && value > 0)
			value = 0.0;
	}

	return value;
}

/*
*	Look up the kern pairs for the first character and see if
*	there is an entry for the second character. Returns the
*	value to kern if any.
*/
static NXCoord getkernvalue(NXFontMetrics *metrics, unsigned char char1, unsigned char char2)
{
	int			enc1, enc2,
				i, kindex, klen;

	NXCoord		value = 0.0;

	enc1 = metrics->encoding[char1]; 
	enc2 = metrics->encoding[char2];
	if (enc1 < metrics->numCharMetrics && enc2 < metrics->numCharMetrics)
	{
		kindex = metrics->charMetrics[enc1].kernPairIndex;
		klen = metrics->charMetrics[enc1].numKernPairs;
		if (kindex+klen < metrics->numKernPairs)
		{
			for (i = kindex; i < kindex + klen; i++)
			{
				if (metrics->hasXYKerns)
				{
					  if (enc2 == metrics->kerns.kernPairs[i].secondCharIndex)
					{
						value = metrics->kerns.kernPairs[i].dx;
						break;
					}
				}
				else
				{
					if (enc2 == metrics->kerns.kernXPairs[i].secondCharIndex)
					{
						value = metrics->kerns.kernXPairs[i].dx;
						break;
					}
				}
			}
		}
	}

	return value;
}

static NXCoord fillkernwidths(char *str, NXFontMetrics *metrics, float *array, ShowStruct *s, float fontsize, int *kerns)
{
	int			i, len;

	NXCoord		value,
				kernwidth;

	kernwidth = 0.0;
	if (str && metrics)
	{
		len = strlen(str) -1;
		for (i = 0; i < len; i++)
		{
			if (value = getkernvalue(metrics, (unsigned char) str[i], (unsigned char) str[i+1]))
			{
				value = value * fontsize;
				if (array)
					array[i] += value;
				else if (s)
					AddPairKern(s, i+1, value);
			
				kernwidth += value;
				(*kerns)++;
			}
		}
	}

	return kernwidth;
}

/*
*	Go through the line and insert the appropriate value into the array.
*	The value placed in is dependent on whether we are kerning or not
*	and whether we are using xshow or not. If kerning, then look up each
*	pair in the AFM kerning array. If using xshow, then include the
*	character width in the array as well (xshow takes the displacement from
*	the previously shown character hence the character width).
*	The fontsize is used to scale the kerning value and the character width.
*	In a screenfont, no adjustment is necessary because the values are
*	already scaled to the appropriate values.
*/
static int  characterspacing(id  font, char *str, NXCoord *array, BOOL justify, BOOL kern, BOOL track, BOOL width)
{
	int			kerns = 0;

	float			fontsize;

	NXCoord		kernwidth,
				spacewidth,
				trackwidth;

	NXFontMetrics	*metrics;

	bzero(array, MAX_XSHOW*sizeof(NXCoord));

	metrics = [font  readMetrics:FONTMETRICS];
	if (metrics)
	{
		if (metrics->isScreenFont)
			fontsize = 1;
		else
			fontsize = [font  pointSize];

		kernwidth = spacewidth = trackwidth = 0.0;

		if (kern && metrics->encoding && metrics->charMetrics)
			kernwidth = fillkernwidths(str, metrics, array, NULL, fontsize, &kerns);

		if (track)
			trackwidth = filltrackwidths(str, array, NULL, TRACKVAL);

		if (width && metrics->widths)
			spacewidth = fillstringwidths(str, metrics, array, fontsize, kernwidth, trackwidth);
	
		if (justify)
		{
			if (!width && metrics->widths)
				spacewidth = fillstringwidths(str, metrics, NULL, fontsize,
								kernwidth, trackwidth);
			fillspacewidths(str, array, NULL, spacewidth);
		}
	}

	return kerns;
}

static int  showstructure(id  font, char *str, ShowStruct *s, BOOL justify, BOOL kern, BOOL track)
{
	int			kerns = 0;

	float			fontsize;

	NXCoord		kernwidth,
				spacewidth,
				trackwidth;

	NXFontMetrics	*metrics;

	metrics = [font  readMetrics:FONTMETRICS];
	if (metrics)
	{
		if (metrics->isScreenFont)
			fontsize = 1;
		else
			fontsize = [font  pointSize];

		kernwidth = spacewidth = trackwidth = 0.0;
		if (kern && metrics->encoding && metrics->charMetrics)
			kernwidth = fillkernwidths(str, metrics, NULL, s, fontsize, &kerns);
	
		if (track)
			trackwidth = filltrackwidths(str, NULL, s, TRACKVAL);

		if (justify)
		{
			spacewidth = fillstringwidths(str, metrics, NULL, fontsize, kernwidth, trackwidth);
			fillspacewidths(str, NULL, s, spacewidth);
		}

	}

	return kerns;
}

/*
*	Set the font either to the printer font or the screenfont, turn the trace on
*	if tracing and then mark the time. (The last two steps are for demonstration
*	purposes only. Cycle through the static array of text checking if a line
*	intersects the rectangle passed in. If so then draw it according to the values
*	set in the interface - kerning or no kerning, screen widths or printer widths,
*	xshow or rmoveto/show, etc. 
*/
- showText:(const NXRect *)r
{
	id			font;

	float			 x;

	int			i, j, last,
				chars,
				ElapsedTime;

	NXRect		textRect;

	if (trace)
		DPSTraceContext(DPSGetCurrentContext(), YES);

	font = [Font  newFont:"Times-Roman"  size:fontsize];
	if (outline)
		PSWSetFont("Times-RomanOutline", fontsize);
	else
		[font  set];

	if (screen && [font  screenFont])
		font = [font  screenFont];

	textRect.origin.x = bounds.origin.x;
	textRect.size.width = bounds.size.width;
	textRect.size.height = 2 * fontsize;

	PSsetwindowtype(NX_RETAINED, [window  windowNum]);
	PSWMarkTime();  NXPing();
	for (i = 0; i < NUM_LINES; i++)
	{
		textRect.origin.y = textstart[i][1] - fontsize;
		if (NXIntersectsRect(r, &textRect))
		{
			chars = strlen(textstrings[i]);
			timing_info.chars += chars;
			if (showtype == XSHOW || showtype == RMSHOW)
			{
				timing_info.kerns += characterspacing(font, textstrings[i], charspace, justify,
						kern, track, (showtype == XSHOW));
				if (showtype == XSHOW)
				{
					PSmoveto(textstart[i][0], textstart[i][1]);
					PSxshow(textstrings[i], charspace, chars);
				}
				else
				{
					PSmoveto(textstart[i][0], textstart[i][1]);
					for (j = 0, last = -1, x = 0.0; j < chars; j++)
					{
						if (charspace[j] != 0)
						{
							if (x == 0.0)
								PSWShow(&textstrings[i][last+1], j - last);
							else
								PSWRmovetoShow(x, &textstrings[i][last+1], j - last);
							last = j;
							x = charspace[j];
						}
					}
					if (x == 0.0)
					{
						if (j-last > 0)
							PSWShow(&textstrings[i][last+1], j - last);
					}
					else
					{
						if (j-last-1 > 0)
							PSWRmovetoShow(x, &textstrings[i][last+1], j - last -1);
					}
				}
			}
			else 
			{
				ResetShowStruct(&s);
				AddString(&s, textstrings[i]);
				AddMoveto(&s, 0, textstart[i][0], textstart[i][1]);
				timing_info.kerns += showstructure(font, textstrings[i], &s, justify,
						kern, track);
				ShowAny(&s);
			}
		}
	}
	PSWReturnTime(&ElapsedTime);
	if (trace)
		DPSTraceContext(DPSGetCurrentContext(), NO);
	PSsetwindowtype(NX_BUFFERED, [window  windowNum]);

	timing_info.time += ElapsedTime;
	timing_info.reshows++;

	return self;
}

/*
*	Erase the fields in the interface, whiten the backdrop, check the cases
*	for showing the text and call the showText: method appropriately. 
*/
- drawSelf:(const NXRect *)r :(int) count
{
	BOOL		temp;

	if (eraseFields)
		[self  eraseFields:self];

	PSsetgray(NX_WHITE);
	NXRectFill(r);
	PSsetgray(NX_BLACK);

	if (track)
		PStranslate(-45, 0);
		
	if (compareKern)
	{
		temp = kern;
		kern = YES;
		[self  showText:r];
		
		kern = NO;
		PStranslate(0.0, 10.0);	
		[self  showText:r];
		kern = temp;
	}
	else if (compareWidths)
	{
		temp = screen;
		screen = NO;
		[self  showText:r];
		
		screen = YES;
		PStranslate(0.0, 10.0);	
		[self  showText:r];
		screen = temp;
	}		
	else
		[self  showText:r];
		
	return self;			
}

@end
